/**
 * File SlideshowPanel.java
 * ---------------------------------------------------------
 *
 * Copyright (C) 2012 David Bauske (david.bauske@googlemail.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package net.cl.util.ui;

import java.awt.Component;
import java.awt.Container;
import java.awt.LayoutManager;

import javax.swing.JPanel;

import net.cl.util.anim.Animation1D;
import net.cl.util.anim.AnimationAdapter;
import net.cl.util.anim.Interpolator;

/**
 * Panel that arranges it's components in such way that only one is shown at a time.
 * The other components reside off-screen and can be slided in to create the classic
 * slideshow-like effect.
 *
 * Note that the LayoutManager of this class cannot be changed.
 *
 * @author David
 */
public class SlideshowPanel extends JPanel
{
	// -----------------------------------------------------------------
	// Fields
	// -----------------------------------------------------------------

	private static final long		serialVersionUID	= 8505614716651039894L;

	protected Component				mActiveComponent	= null;
	protected SlideshowContentPane 	mContentPanel;

	protected SlideOrientation		mOrientation;
	protected int					mComponentGap		= 0;

	protected Animation1D			mAnimation			= new Animation1D();
	protected boolean				mAnimateTransitions	= true;

	private SlideshowPanelLayout	mPanelLayout;
	private SlideshowRowLayout		mRowLayout;

	// -----------------------------------------------------------------
	// Public methods
	// -----------------------------------------------------------------

	/**
	 * Creates a new SlideshowPanel.
	 *
	 * @param pOrientation The orientation in which the slides should
	 * 		slide (Yeah, this description is awesome!).
	 */
	public SlideshowPanel(SlideOrientation pOrientation)
	{
		this.mOrientation = pOrientation;

		this.mPanelLayout = new SlideshowPanelLayout(pOrientation);
		super.setLayout(this.mPanelLayout);

		this.mRowLayout = new SlideshowRowLayout(pOrientation);
		this.mContentPanel = new SlideshowContentPane(this.mRowLayout);
		super.addImpl(this.mContentPanel, null, 0);

		this.mPanelLayout.setContentPanel(mContentPanel);

		this.mAnimation.addListener(new AnimationAdapter()
		{
			@Override
			public void onProgress(Animation1D pAnim, double pValue)
			{
				SlideshowPanel.this.mOrientation.setLocation(
						SlideshowPanel.this.mContentPanel, pValue);
			}
		});
	}

	/**
	 * @return The currently active component. May be null.
	 */
	public Component getActiveComponent()
	{
		return this.mActiveComponent;
	}

	/**
	 * Sets the currently active component.
	 * <p>
	 * This methods is short for <tt>setActiveComponent(pComponent, true);</tt>
	 * </p>
	 *
	 * @param pComponent The component to set as active. Must be part of the panel.
	 */
	public void setActiveComponent(Component pComponent)
	{
		this.setActiveComponent(pComponent, true);
	}

	/**
	 * Sets the currently active component.
	 *
	 * @param pComponent The component to set as active. Must be part of the panel.
	 * @param pAnimated Whether the state transition should be animated. Works only if
	 * 			animations are generally allowed on this SlideshowPanel.
	 */
	public void setActiveComponent(Component pComponent, boolean pAnimated)
	{
		this.mActiveComponent = pComponent;
		this.mPanelLayout.setActiveComponent(pComponent);

		if(this.mAnimateTransitions && pAnimated)
		{
			this.mAnimation.start(
					this.mOrientation.getStartValue(this.mContentPanel),
					this.mOrientation.getTargetValue(this, this.mActiveComponent, this.mContentPanel));
		}
		else
		{
			this.doLayout();
		}
	}

	/**
	 * Moves the currently active component one step further - depending on the panel's
	 * orientation either to the bottom or to the right.
	 */
	public void showNext()
	{
		int count = this.mContentPanel.getComponentCount();
		Component[] components = this.mContentPanel.getComponents();

		for(int i = 0; i < count; ++i)
		{
			if(components[i] == this.mActiveComponent)
			{
				// Active component found.
				if(i + 1 < count)
				{
					// Moving one step further is possible
					this.setActiveComponent(components[i+1]);
				}
				return;
			}
		}

		// Active component not found yet? Take the first one...
		if(count > 0)
		{
			this.setActiveComponent(components[0]);
		}
	}

	/**
	 * Moves the currently active component one step back - depending on the panel's
	 * orientation either to the top or to the left.
	 */
	public void showPrevious()
	{
		int count = this.mContentPanel.getComponentCount();
		Component[] components = this.mContentPanel.getComponents();

		for(int i = 0; i < count; ++i)
		{
			if(components[i] == this.mActiveComponent)
			{
				// Active component found.
				if(i - 1 >= 0)
				{
					// Moving one step back is possible
					this.setActiveComponent(components[i-1]);
				}
				return;
			}
		}

		// Active component not found yet? Take the first one...
		if(count > 0)
		{
			this.setActiveComponent(components[0]);
		}
	}

	/**
	 * Sets the interpolator to use when animating the panel.
	 * @param pInterpolator The interpolator object to use.
	 */
	public void setInterpolator(Interpolator pInterpolator)
	{
		this.mAnimation.setInterpolator(pInterpolator);
	}

	/**
	 * Sets the duration in seconds a state transition should take.
	 * @param pDuration The duration in seconds.
	 */
	public void setAnimationDuration(double pDuration)
	{
		this.mAnimation.setDuration(pDuration);
	}

	/**
	 * Sets whether panel transitions should be animated or not.
	 * @param pAnimate true to perform animations, false to move components to their new positions
	 * 			without smoothing.
	 */
	public void setAnimateTransitions(boolean pAnimate)
	{
		this.mAnimateTransitions = pAnimate;
	}

	/**
	 * @return true if this will animate any panel movements.
	 */
	public boolean isAnimatingTransitions()
	{
		return this.mAnimateTransitions;
	}

	/**
	 * Sets the gap in pixels between two components in the slideshow.
	 * @param pGap The gap in pixels to use.
	 */
	public void setComponentGap(int pGap)
	{
		this.mComponentGap = pGap;
		this.mRowLayout.setComponentGap(pGap);
		this.mContentPanel.setComponentGap(pGap);
	}

	/**
	 * @return The gap in pixels between two components.
	 */
	public int getComponentGap()
	{
		return this.mComponentGap;
	}

	// -----------------------------------------------------------------
	// Protected methods
	// -----------------------------------------------------------------

	@Override
	protected void addImpl(Component pComp, Object pConstraints, int pIndex)
	{
		// Object should not be added to the SlideshowPanel, but rather to the underlying contentPanel.
		this.mContentPanel.add(pComp, pConstraints, pIndex);

		if(this.mActiveComponent == null)
		{
			this.mActiveComponent = pComp;
		}
	}

	// -----------------------------------------------------------------
	// Unsupported operations
	// -----------------------------------------------------------------

	/**
	 * Setting the layout manager is not supported. The method will perform
	 * no action.
	 */
	@Override
	public void setLayout(LayoutManager pManager)
	{
	}

	// -----------------------------------------------------------------
	//  Inner types
	// -----------------------------------------------------------------

	public enum SlideOrientation
	{
		/**
		 * Lay out vertically (one on top of the other)
		 */
		VERTICAL
		{
			@Override
			double getStartValue(JPanel pPanel)
			{
				return pPanel.getY();
			}

			@Override
			double getTargetValue(Container pContainer, Component pTargetComponent, SlideshowContentPane pContentPanel)
			{
				int destY = pContainer.getInsets().top - pTargetComponent.getY();
				if(pContentPanel.getRequiredHeight() > pContentPanel.getMaxHeightPerComponent() &&
						destY - pContentPanel.getMaxHeightPerComponent() < -pContentPanel.getRequiredHeight())
				{
					destY = -pContentPanel.getRequiredHeight() + pContentPanel.getMaxHeightPerComponent() + pContainer.getInsets().top;
				}
				return destY;
			}

			@Override
			void setLocation(Component pComp, double pValue)
			{
				pComp.setLocation(pComp.getX(), (int) pValue);
			}
		},

		/**
		 * Lay out horizontally (one on the right of the other)
		 */
		HORIZONTAL
		{
			@Override
			double getStartValue(JPanel pPanel)
			{
				return pPanel.getX();
			}

			@Override
			double getTargetValue(Container pContainer, Component pTargetComponent, SlideshowContentPane pContentPanel)
			{
				int destX = pContainer.getInsets().left - pTargetComponent.getX();
				if(pContentPanel.getRequiredWidth() > pContentPanel.getMaxWidthPerComponent() &&
						destX - pContentPanel.getMaxWidthPerComponent() < -pContentPanel.getRequiredWidth())
				{
					// Slided too far left
					destX = -pContentPanel.getRequiredWidth() + pContentPanel.getMaxWidthPerComponent() + pContainer.getInsets().left;
				}
				return destX;
			}

			@Override
			void setLocation(Component pComp, double pValue)
			{
				pComp.setLocation((int) pValue, pComp.getY());
			}
		};

		abstract double getStartValue(JPanel pPanel);
		abstract double getTargetValue(Container pContainer, Component pTargetComponent, SlideshowContentPane pContentPanel);
		abstract void 	setLocation(Component pComp, double pValue);
	}
}
